//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <eltypes.h>
#include <errno.h>
#include <ctype.h>
#include <_pubcrt.h>
#define _NO_INCLIST
#include <elasys_server.h>

_ELASTOS_NAMESPACE_USING

EXTERN_C wchar_t  *g_DevpathBase;
//
// The other definition is in \kernel\major\inc\mantle\asdef.h
//
wchar_t *g_DevpathBase = (wchar_t *)0x3fffa000;      // 1GB - 32KB + 8KB
#define USER_DEVPATH_BASE       g_DevpathBase     // 1GB - 32KB + 8KB
#ifndef _win32
#define USER_DEVPATH_SIZE       0x1A00      // 6.5KB
#define MAX_DEVPATH_ITEM        12
#define BLOCK_LENGTH            544
#else   //support 26 disks in NT
#define USER_DEVPATH_SIZE       0x2000      // 8KB
#define MAX_DEVPATH_ITEM        26
#define BLOCK_LENGTH            260
#endif
#define MAX_DEVICE_NUM          26

//
//  Memory structure of device_path segment:
//
//  |<-- 4-->|<----  104  ------>|<--- 544 ---->|      |<--- 544 ---->|
//  +--------+-------------------+--------------+------+--------------+
//  | marker | array of dev_path | path block 0 | ...  |path block 11 |         |
//  +--------+-------------------+--------------+------+--------------+
//  ^                            ^
//  |                            |__ pBeginOfBuffer
//  |____ Marker

#define DEVPATH_VALUE(disk) \
        (*((wchar_t **)((int *)USER_DEVPATH_BASE + 1) + (disk - 1)))
#define IS_BLANK(index)    (!((*(UInt32 *)USER_DEVPATH_BASE) & (1 << index)))
#define SET_MARKER(index)      ((*(UInt32 *)USER_DEVPATH_BASE) |= (1 << index))
#define DEL_MARKER(index)      ((*(UInt32 *)USER_DEVPATH_BASE) &= ~(1 << index))

EXTERN_C IMutex *EnvironMutex();

#ifdef __cplusplus
extern "C" {
#endif

wchar_t ** CDECL __p__g_DevpathBase(void)
{
    return &g_DevpathBase;
}

Boolean CDECL SynDevpath(int drive)
{
    IDiskManager *pDiskMgr = NULL;

    ECode ec = CrtFindService(L"diskmgr", (IInterface **)&pDiskMgr);
    if (FAILED(ec)) return FALSE;

    Int32 dwDisks;
    pDiskMgr->GetDisks(DiskType_AllDisk, &dwDisks);
    pDiskMgr->Release();

    byte * pBeginOfBuffer= (byte *)((wchar_t **)((int *)USER_DEVPATH_BASE + 1)
                + MAX_DEVICE_NUM);
    if (dwDisks & (1 << drive)) {
        if (!DEVPATH_VALUE(drive)) {
            int i;
            for (i = 0; i < MAX_DEVPATH_ITEM; i++) {
                if (IS_BLANK(i)) {
                    DEVPATH_VALUE(drive) = (wchar_t *)(pBeginOfBuffer
                        + BLOCK_LENGTH * i);
                    SET_MARKER(i);
                    break;
                }
            }

            if (i >= MAX_DEVPATH_ITEM) return FALSE;

            wchar_t *pwszDevpath = DEVPATH_VALUE(drive);
            pwszDevpath[0] = drive - 1 + L'A';
            pwszDevpath[1] = L':';
            pwszDevpath[2] = WC_PATH_SEPARATOR;
            pwszDevpath[3] = L'\0';
        }
    }
    else {
        //The disk have been deleted
        if (DEVPATH_VALUE(drive)) {
            int index = ((byte *)DEVPATH_VALUE(drive) - pBeginOfBuffer)
                            /BLOCK_LENGTH;
            DEL_MARKER(index);
            DEVPATH_VALUE(drive) = NULL;
        }
        return FALSE;
    }

    return TRUE;
}

Boolean CDECL SetDevpath(int drive, wchar_t *pwszDevpath)
{
    EnvironMutex()->Lock(NULL);
    if (SynDevpath(drive)) {
        wcscpy(DEVPATH_VALUE(drive), pwszDevpath);
        EnvironMutex()->Unlock();
        return TRUE;
    }
    else {
        EnvironMutex()->Unlock();
        return FALSE;
    }
}

wchar_t * CDECL GetDevpath(int drive, wchar_t *pwszDevpath, int maxlen)
{
    EnvironMutex()->Lock(NULL);
    if (SynDevpath(drive) && ((size_t)maxlen > wcslen(DEVPATH_VALUE(drive)))) {
        wcscpy(pwszDevpath, DEVPATH_VALUE(drive));
    }
    else {
        pwszDevpath = NULL;
    }
    EnvironMutex()->Unlock();
    return pwszDevpath;
}

/*
 * Create an absolute or full path name for the specified relative path name.
 * Back slash '/' is used as path separator
 */

ECode WFullPath(wchar_t *absPath, const wchar_t *relPath, int maxlen)
{
    if (!absPath) {
        return ECODE_FROM_ERRNO(EINVAL);
    }

    wchar_t *pwszCwd = NULL;
    WStringBuf_<_MAX_PATH> wsAppletDir;
    ECode ec = CrtGetCurrentAppletDir(&wsAppletDir);
    if (FAILED(ec)) pwszCwd = NULL;
    else pwszCwd = wsAppletDir.GetPayload();

    if (!pwszCwd || *pwszCwd == L'\0') {
        pwszCwd = WGetEnv(L"CD");
    }

    if (!pwszCwd || wcslen(pwszCwd) >= _MAX_PATH) {
        return ECODE_FROM_ERRNO(ENOENT);
    }

    if (!relPath || !*relPath) {// no work to do
        wcscpy(absPath, pwszCwd);
        return NOERROR;
    }

    wchar_t *buf, *src, *dest, *ptr;
    int len, rlen;
    rlen = wcslen(relPath);
    buf = new wchar_t[(_MAX_PATH + rlen + 1)];
    if (!buf) {
        return ECODE_FROM_ERRNO(ENOMEM);
    }

    if (relPath[1] != L':') {
        wcscpy(buf, pwszCwd);

        //if first char is '/' or '\' then only get disk symbol
        if (IS_PATH_SEPARATOR(relPath[0])) {
            buf[3] = L'\0';
        }

        len = wcslen(buf);
        if (len > 3) {
            buf[len++] = WC_PATH_SEPARATOR;
        }

        if (IS_PATH_SEPARATOR(*relPath)) {
            relPath++;
            rlen--;
        }
        wcscpy(buf + len, relPath);
    }
    else {
        if (!isalpha(relPath[0])) {
            delete buf;
            return ECODE_FROM_ERRNO(ENOENT);
        }

        if (!IS_PATH_SEPARATOR(relPath[2])) {
            if (GetDevpath(toupper(relPath[0]) - L'A' + 1, buf, maxlen)) {
                len = wcslen(buf);
                if (len > 3) {
                    buf[len++] = WC_PATH_SEPARATOR;
                }
            }
            else {
                wcsncpy(buf, relPath, 2);
                buf[2] = WC_PATH_SEPARATOR;
                len = 3;
            }

            wcscpy(buf + len, relPath + 2);
        }
        else {
            wcscpy(buf, relPath);
        }
    }

    buf[0] = toupper(buf[0]);

    len = wcslen(buf);
    assert(len >= 3);

    assert(L':' == buf[1]);
    buf[2] = WC_PATH_SEPARATOR;

    ptr = buf + 3;      // Skip "C:/", src after path separator
    src = ptr;
    dest = ptr;

    while (IS_PATH_SEPARATOR(*ptr)) {
        ptr++;
    }
    src = ptr;

    while (1) {
        if (IS_PATH_SEPARATOR(*ptr) || !(*ptr)) {
            len = ptr - src;
            if ((len == 1) && (*src == L'.')) {
                //do nothing;
            }
            else if ((len == 2) && (*src == L'.') && (*(src + 1) == L'.')) {
                if (L':' != *(dest - 2)) {
                    dest -= 2;
                    while (WC_PATH_SEPARATOR != *dest) {
                        dest--;
                        assert(dest > buf);
                    }
                    dest++;
                }
            }
            else if (len) {
                if (src > dest) {
                    memmove(dest, src, len * sizeof(wchar_t));
                }
                dest += len;
                if (*ptr) {
                    *dest = WC_PATH_SEPARATOR;
                    dest++;
                }
            }

            while (IS_PATH_SEPARATOR(*ptr)) ptr++;
            src = ptr;

            //Break the loop;
            if (!(*ptr)) break;
        }
        else {
            ptr++;
        }
    }

    *dest = L'\0';

    if (wcslen(buf) >= (size_t)maxlen) {
        delete buf;
        return ECODE_FROM_ERRNO(ENOENT);
    }

    wcscpy(absPath, buf);

    delete buf;
    return NOERROR;
}

#ifdef __cplusplus
}
#endif
